

Rem
	bbdoc: State based game for game2d
	about: Add user setup code in a StartUp() method when extending this type
	States are added by calling AddGameState()
	Start the game by calling Start()
	User cleanup code is run on shutdown by adding a CleanUp() method to the extended type
end rem
Type TGame

	'#region setup
	'---------------------------------------------------------

	'the game title. Appears in the window title.
	Field _title:String

	'sound channels
	Field _soundChannels:TChannel[]

	'game resolution. not physical resolution
	Field _gameWidth:Int
	Field _gameHeight:Int

	'collection of game states, by index.
	Field _gameStates:TBag

	'The active game state.
	Field _currentGameState:TState

	'The state we're going to.
	Field _nextGameState:TState

	'The transition to use when leaving current state.
	Field _leaveTransition:TTransition

	'The transition to use when entering the next state.
	Field _enterTransition:TTransition

	'bool. true when virtual resolution is enabled.
	Field _virtualResolution:Int

	'fixed game step timer
	Field _timer:TFixedTime

	'the in-use graphics object
	Field _graphics:TGraphics

	'statistics
	Field _startTime:Int
	Field _updates:Int
	Field _framesRendered:Int

	'bools
	Field _paused:Int
	Field _running:Int
	Field _vsync:Int

	'input device
	Field _input:TInput

	'configuration file
	field _config:TINIFile

	'game font is rendered at this scale. default is 1.0
	Field _fontScale:Float


	Method New()
		_timer = New TFixedTime
        _gameStates = New TBag

        'create managers
        TResourceManager.GetInstance()
        TEntityManager.GetInstance()

        _input = New TInput
        _input._game = Self

        _fontScale = 1.0

		Self.LoadConfig()
		Self.ApplyConfig()

		SetTitle("Powered by Game2d!")
	End Method


	Rem
		bbdoc: Sets up and starts the game
		about: Additional user setup code can be added to the StartUp() method
	endrem
	Method Start()

		'create sound channels
		'and set audio drivers.
		Self.SetupSound()

		'run user setup code, loading images, sound, etc.
		'into the resource manager.
		Self.Startup()

		'set running flags
		_running = True
		_paused = False
		_vsync = -1

		'reset statistics
		_startTime = MilliSecs()
		_framesRendered = 0
		_updates = 0

		'reset the step timer.
		_timer.Reset()

		'run the Enter() method in the default state
		'otherwise this never gets called!
		_currentGameState.Enter()

		'enter the run loop
		Self.Run()
	End Method



	Rem
		bbdoc: Sets game name
	endrem
	Method SetTitle(newName:String)
		?Debug
		newName:+" - debug build"
		?
		_title = newName
		AppTitle = newName
	End Method


	Rem
		bbdoc: Returns game name
		returns: String
	endrem
	Method GetTitle:String()
		Return _title
	End Method

	'#endregion


	'#region configuration
	'------------------------------------------------------------------


	Rem
		bbdoc:   Loads the game config file.
		about:   The default name for the config file is "config.ini" and
				 it is located in the game executable directory
		returns:
	EndRem
	Method LoadConfig()

		'config file is located in the game folder.
		_config = TINIFile.Create( AppDir + "/config.ini")

		'use these default settings when config file cannot be loaded
		If Not _config.Load()
			_config.CreateMissingEntries(True)
			_config.AddSection("Input")
			_config.SetIntValue("Input", "PAUSE", KEY_P)
			_config.SetIntValue("Input", "CANCEL", KEY_ESCAPE)

			_config.AddSection("Graphics")
			_config.SetBoolValue("Graphics", "Windowed", "true")
			_config.SetIntValue("Graphics", "Width", 800)
			_config.SetIntValue("Graphics", "Height", 600)
			_config.SetIntValue("Graphics", "Depth", 24)
			_config.SetIntValue("Graphics", "Herz", 60)
		End If
	End Method



	Rem
		bbdoc:   Applies the settings as set in the current ini file
		about:   The graphics screen is created and the pause and cancel controls
		are added.
		returns:
	EndRem
	Method ApplyConfig()

		'create graphics
		local w:Int = _config.GetIntValue("Graphics", "Width")
		local h:Int = _config.GetIntValue("Graphics", "Height")
		local d:Int = _config.GetIntValue("Graphics", "Depth")
		local hz:Int = _config.GetIntValue("Graphics", "Herz")
		local windowed:Int = _config.GetBoolValue("Graphics", "Windowed")

		if windowed
			Self.SetPhysicalResolution(w, h, 0, hz, False)
		Else
			Self.SetPhysicalResolution(w, h, d, hz, False)
		endif

		'add default controls to input device
		Local keyValue:Int
		keyValue = _config.GetIntValue("Input", "PAUSE")
		_input.AddControl(TControl.Create("PAUSE",keyValue))
		keyValue = _config.GetIntValue("Input", "CANCEL")
		_input.AddControl(TControl.Create("CANCEL",keyValue))
	End Method


	Rem
		bbdoc:   Saves the settings in the ini file to disk
		about:   Graphics and input settings are saved
		returns:
	EndRem
	Method SaveConfig()
		'graphics settings are already in the ini file.
		'See SetPhysicalResolution()

		'get input settings into ini file
		_input.ToIniFile(_config)

		'save file
		_config.Save()
	End Method

	'#endregion


	'#region sound
	'------------------------------------------------------------------

	Rem
		bbdoc:   Sets up the sound system
		about:   This method is called by TGame.Start()
		Sound driver order: OpenAL, FreeAudio (Linux),
		DirectSound, FreeAudio, OpenAL (Win32)
		returns:
	EndRem
    Method SetupSound()

		'enable openal audio
		'to add it to the list of audio drivers
		EnableOpenALAudio()

		'bool
		Local audioSet:Int = True

		?Linux
		If AudioDriverExists("OpenAL")
			SetAudioDriver("OpenAL")
		ElseIf AudioDriverExists("FreeAudio")
			SetAudioDriver("FreeAudio")
		Else
			audioSet = False
		EndIf

		?Win32
		If AudioDriverExists("DirectSound")
			SetAudioDriver("DirectSound")
		ElseIf AudioDriverExists("FreeAudio")
			SetAudioDriver("FreeAudio")
		ElseIf AudioDriverExists("OpenAL")
			SetAudioDriver("OpenAL")
		Else
			audioSet = False
		EndIf
		?

		If Not audioSet Then RuntimeError("Error: Cannot set audio driver.")

		'allocate 64 channels.
		_soundChannels = New TChannel[64]
		For Local index:Int = 0 To _soundChannels.Length - 1
			_soundChannels[index] = AllocChannel()
		Next

    End Method


    Method CleanUpSound()
 '   	For Local index:Int = 0 To _soundChannels.Length - 1
''			Local channel:TChannel = _soundChannels[index]
'			channel.Free
'		Next
    EndMethod



	Rem
		bbdoc: Plays a previously loaded sound on passed channel or random channel if none provided
		returns: TChannel used to play the sound
	endrem
	Method PlaySound:TChannel(sound:TSound, channel:TChannel, volume:Float)
'	Method PlaySound:TChannel(soundName:String, groupName:String, channel:TChannel, volume:Float)
		If channel = Null
			'find a free channel
			For Local index:Int = 0 To _soundChannels.Length - 1
				Local c:TChannel = _soundChannels[index]
				If Not c.Playing()
					channel = c
					Exit
				EndIf
			Next

			'overload. grab first channel
			If channel = Null Then channel = _soundChannels[0]
		EndIf

		'get sound from resource manager
	'	local sound:TSound = TResourceManager.GetInstance().GetSound(soundName, groupName)

		'play sound
		channel.SetVolume(0.0)
		CueSound(sound, channel)
		channel.SetVolume(volume)
		ResumeChannel(channel)
		Return channel
	End Method


	Rem
		bbdoc: Stops all sound channels.
	endrem
	Method StopAllSound()
		For Local index:Int = 0 To _soundChannels.Length - 1
			Local channel:TChannel = _soundChannels[index]
			?win32
			If channel.Playing() Then channel.Stop()
			?Linux
			If channel.Playing() Then channel.SetPaused(true)
			?
		Next
	End Method

	'#endregion


	'#region input
	'------------------------------------------------------------------

	'adds a control to the input device
	'uses value in ini file if it exists
	Method AddKeyControl(c:TControl)
		_input.AddControl(c)

		if _config.ParameterExists("Input", c.GetName())
			c.key = _config.GetIntValue("Input", c.GetName())
		endif
	EndMethod


	Rem
		bbdoc:   Returns a control by name.
		about:
		returns: TControl
	EndRem
	Method GetKeyControl:TControl(name:String)
		return _input.GetControl(name)
	end method


	Rem
		bbdoc:   Returns control key status.
		about:
		returns: Int
	EndRem
	Method KeyControlDown:Int(controlName:String)
		Return _input.GetDown(controlName)
	EndMethod


	Rem
		bbdoc:   Returns control hits since last update.
		about:
		returns: Int
	EndRem
	Method KeyControlHit:Int(controlName:String)
		Return _input.GetHit(controlName)
	EndMethod


	Method StartConfigureControls()
		_input.StartConfiguring()
	EndMethod


	'#endregion


	'#region runtime
	'------------------------------------------------------------------

	Rem
		bbdoc:   Main loop
		about:   Do not call this method. It is called by Start()
		returns:
	EndRem
	Method Run()
		While _running = True
			If Not _paused
				_timer.Update()
				While _timer.TimeStepNeeded()
					Self.Update(_timer.GetDeltaTime())
					_updates:+ 1
				Wend
			End If

			'toggle pause mode.
			'don't do this when input is being reconfigured
			If KeyHit(KEY_P) and not _input.configuring
				Self.SetPaused(Not _paused)

				'coming out of pause?
				'reset the fixed timer to avoid tweening 'jumps'
				If _paused = False Then _timer.Reset()
			EndIf

			Cls
			Self.Render(_timer.GetTweening())

			Flip(_vsync)

			If AppTerminate() Then _running = False
		Wend
		Self.Stop()
	End Method


	Rem
		bbdoc:   Game logic update
		about:   Updates the current game state with passed delta time
		returns:
	EndRem
	Method Update(delta:Double)
		If _leaveTransition
			_leaveTransition.Update(delta)
			If _leaveTransition.IsComplete()
				_currentGameState.Leave()
				_currentGameState = _nextGameState
				_nextGameState = Null
				_leaveTransition = Null
				_currentGameState.Enter()
				If _enterTransition Then _enterTransition.Initialize()
			Else
				Return
			End If
		End If
		If _enterTransition
			_enterTransition.Update(delta)
			If _enterTransition.IsComplete()
				_enterTransition = Null
			Else
				Return
			End If
		End If

		_currentGameState.PreUpdate(delta)

		'update state
		_currentGameState.Update(delta)

		'update input device
		_input.Update()

		'run entities update methods
		TEntityManager.GetInstance().Update()

		_currentGameState.PostUpdate(delta)

	End Method


	Rem
		bbdoc:   Stops the game.
		about:   Gets called when the game is stopped.
		returns:
	EndRem
	Method Stop()

		'run user cleanup code
		'user settings should be passed to the ini file at this stage as well
		Self.CleanUp()

		Self.SaveConfig()

		_timer = Null
		Self.StopAllSound()

		'get rid of managers
		TEntityManager.GetInstance().Destroy()
		TResourceManager.GetInstance().Destroy()

		_gameStates.Clear()
		EndGraphics()
	End Method


	Rem
		bbdoc: Returns true when game is transitioning between states.
		returns: Int
	endrem
	Method Transitioning:Int()
		Return _enterTransition Or _leaveTransition <> Null
	End Method


	Rem
	bbdoc: Returns the current game state.
	endrem
'	Method GetCurrentState:TState()
'		Return _currentGameState
'	End Method


	Rem
	bbdoc: Returns the ID of the current game state.
	endrem
'	Method GetCurrentStateID:Int()
'		Return _currentGameState.GetId()
'	End Method


	Rem
		bbdoc: Returns game state by specified ID.
		returns: TState
	endrem
	Method GetGameStateByID:TState(id:Int)
		Return TState(_gameStates.Get(id))
	End Method


	Rem
		bbdoc: Enters a gamestate with the provided transitions.
	endrem
	Method EnterState(id:Int, enter:TTransition = Null, leave:TTransition = Null)
		If Not enter Then enter = New TTransitionEmpty
		If Not leave Then leave = New TTransitionEmpty
		_enterTransition = enter
		_leaveTransition = leave

		_nextGameState = GetGameStateByID(id)
		If Not _nextGameState Then RuntimeError "Cannot find state with id:" + id

		_leaveTransition.Initialize()
	End Method


	Rem
		bbdoc: Adds state to this game with specified id.
		about: First state added is the default state when the game starts.
	endrem
	Method AddGameState(gameState:TState, id:Int)
		_gameStates.Set(id, gameState)
		gameState.SetId(id)
		gameState.SetGame(Self)
		If Not _currentGameState Then _currentGameState = gameState
	End Method


	Rem
	bbdoc: Sets engine pause mode.
	endrem
	Method SetPaused(bool:Int)
		_paused = bool
	End Method


	Rem
	bbdoc: Returns true when the engine is paused.
	endrem
	Method IsPaused:Int()
		Return _paused
	End Method


	Rem
		bbdoc: Changes the key in the pause control.
	endrem
	Method SetPauseKey(keycode:Int)
		_input.GetControl("PAUSE").key = keycode
	End Method


	Rem
		bbdoc: Calling this will stop the game on the next update.
	endrem
	Method RequestStop()
		_running = False
	End Method


	Rem
		bbdoc: Sets game update frequency, in times per second.
	endrem
	Method SetUpdateFrequency(frequency:Int)
		_timer.SetUpdateFrequency(frequency)
	End Method

	'#endregion	runtime


	'#region graphics and render
	'---------------------------------------------------


	Method Render(tween:Double)
		_currentGameState.PreRender(tween)

		TEntityManager.GetInstance().Render(tween)

		_currentGameState.Render(tween)

		If _input.configuring Then _input.Render()

		If _leaveTransition
			_leaveTransition.Render(Self)
		ElseIf _enterTransition
			_enterTransition.Render(Self)
		EndIf

		_currentGameState.PostRender(tween)

	End Method


	Rem
		bbdoc: Sets vertical blank flag.
	endrem
	Method SetVSync(bool:Int)
		_vsync = bool
	End Method


	Rem
		bbdoc: Returns vertical blank flag.
		returns: Int
	endrem
	Method GetVSync:Int()
		Return _vsync
	End Method


	Rem
		bbdoc: Sets physical screen resolution and graphics driver.
		about: No depth or a depth of 0 means a Windowed game.
	endrem
	Method SetPhysicalResolution(w:Int, h:Int, depth:Int = 0, herz:Int = 60, openGL:Int = False)

		'choose graphics driver according to platform and preference:
		'linux: opengl
		'windows: dx9, opengl

		?Linux
		SetGraphicsDriver(GLMax2DDriver())
		?Win32
		'set GL as driver when requested
		If openGL Then SetGraphicsDriver(GLMax2DDriver())
		'not succesful, or no GL wanted?
		'try dx9, and GL again if dx9 does not work
		If Not GetGraphicsDriver()
			SetGraphicsDriver(D3D9Max2DDriver())
			If Not GetGraphicsDriver()
				SetGraphicsDriver(GLMax2DDriver())
			End If
		End If
		?

		If Not GetGraphicsDriver()
			RuntimeError("Could not select a graphics driver!")
		End If

		'try to create a default window when the passed
		'graphics mode does not exist.
		If Not GraphicsModeExists(w, h, depth, herz)
			If GraphicsModeExists(800, 600, 0, 0)
				w = 800
				h = 600
				depth = 0
				herz = 60
			Else
				RuntimeError("Could not create graphics mode!")
			End If
		End If
		_graphics = Graphics(w, h, depth, herz)


		'succesfull!
		'pass settings into ini file so these can be saved when the game exits
		_config.SetIntValue("Graphics", "Width", w)
		_config.SetIntValue("Graphics", "Height", h)
		_config.SetIntValue("Graphics", "Depth", depth)
		_config.SetIntValue("Graphics", "Herz", herz)

		if openGL = true
			_config.SetBoolValue("Graphics", "OpenGL", "true")
		Else
			_config.SetBoolValue("Graphics", "OpenGL", "false")
		endif

		if depth = 0
			_config.SetBoolValue("Graphics", "Windowed", "true")
		Else
			_config.SetBoolValue("Graphics", "Windowed", "false")
		endif

	End Method


	Rem
		bbdoc: Sets game resolution.
		about: This is a virtual resolution, independant from physical resolution,
		which is set with Game.SetPhysicalResolution()
	endrem
	Method SetGameResolution(w:Int, h:Int)
		_virtualResolution = True
		_gameWidth = w
		_gameHeight = h
		SetVirtualResolution(w, h)
	End Method


	Rem
		bbdoc: Returns game width
	endrem
	Method GetGameWidth:Int()
		If _virtualResolution Then Return _gameWidth
		Return GraphicsWidth()
	End Method


	Rem
		bbdoc: Returns game height
	endrem
	Method GetGameHeight:Int()
		If _virtualResolution Then Return _gameHeight
		Return GraphicsHeight()
	End Method


	Rem
		bbdoc: Renders text in the current game font
		about: Text can be centered, with a shadow.
	endrem
	Method RenderText(text:String, xpos:Float, ypos:Float, centered:Int = False, ..
					  shadow:Int = False, shadowDistance:Float = 1.0)

'		Local xscale:Float
'		Local yscale:Float
'		GetScale(xscale, yscale)
		SetScale(_fontScale, _fontScale)

'		If centered Then xpos = Self.GetGameWidth() / 2 - ((TextWidth(text) * xscale) / 2)
		If centered Then xpos = Self.GetGameWidth() / 2 - ((TextWidth(text) * _fontScale) / 2)
		If shadow
			Local r:Int, g:Int, b:Int
			GetColor(r, g, b)
			SetColor 0, 0, 0
			DrawText(text, xpos, ypos + shadowDistance * _fontScale)'yscale)
			SetColor (r, g, b)
		End If
		DrawText(text, xpos, ypos)
	End Method


	Rem
		bbdoc:   Sets game font scale.
		about:
		returns:
	EndRem
	Method SetFontScale(value:Float)
		_fontScale = value
	EndMethod


	'#endregion graphics and render


	'#region user hooks
	'----------------------------------------------------------

	Rem
		bbdoc: User hook to perform game startup logic.
		about: This method is called when the game starts.
	endrem
	Method Startup()
	End Method


	Rem
		bbdoc: User hook to perform game shut down logic.
		about: This method is called when the game stops.
	endrem
	Method CleanUp()
	End Method

	'#endregion  user hooks


	'#region debug
	'-----------------------------------------------------------

	'#endregion debug

End Type

